/*=============================================================================
  ShaderParse.cpp : implementation of the Shaders parser part of shaders manager.
  Copyright (c) 2001 Crytek Studios. All Rights Reserved.

  Revision history:
    * Created by Honich Andrey

=============================================================================*/

#include "StdAfx.h"
#include "I3DEngine.h"
#include "CryHeaders.h"

#if defined(WIN32) || defined(WIN64)
#include <direct.h>
#include <io.h>
#elif defined(LINUX)
#endif


//============================================================
// Compile functions
//============================================================

SShaderGenBit *CShaderMan::mfCompileShaderGenProperty(char *scr)
{
  char* name;
  long cmd;
  char *params;
  char *data;

  SShaderGenBit *shgm = new SShaderGenBit;

  enum {eName=1, eProperty, eDescription, eMask, eHidden, eRuntime, ePrecache, eDependencySet, eDependencyReset, eDependFlagSet, eDependFlagReset, eAutoPrecache, eLowSpecAutoPrecache};
  static STokenDesc commands[] =
  {
    {eName, "Name"},
    {eProperty, "Property"},
    {eDescription, "Description"},
    {eMask, "Mask"},
    {eHidden, "Hidden"},
    {ePrecache, "Precache"},
    {eRuntime, "Runtime"},
    {eAutoPrecache, "AutoPrecache"},
    {eLowSpecAutoPrecache, "LowSpecAutoPrecache"},
    {eDependencySet, "DependencySet"},
    {eDependencyReset, "DependencyReset"},
    {eDependFlagSet, "DependFlagSet"},
    {eDependFlagReset, "DependFlagReset"},

    {0,0}
  };

	shgm->m_PrecacheNames.reserve(45);

  while ((cmd = shGetObject (&scr, commands, &name, &params)) > 0)
  {
    data = NULL;
    if (name)
      data = name;
    else
    if (params)
      data = params;

    switch (cmd)
    {
      case eName:
        shgm->m_ParamName = data;
        shgm->m_dwToken = GetCRC32Gen().GetCRC32(data);
        break;

      case eProperty:
        if (gRenDev->IsEditorMode())
          shgm->m_ParamProp = data;
        break;

      case eDescription:
        if (gRenDev->IsEditorMode())
          shgm->m_ParamDesc = data;
        break;

      case eHidden:
        shgm->m_Flags |= SHGF_HIDDEN;
        break;

      case eRuntime:
        shgm->m_Flags |= SHGF_RUNTIME;
        break;

      case eAutoPrecache:
        shgm->m_Flags |= SHGF_AUTO_PRECACHE;
        break;
      case eLowSpecAutoPrecache:
        shgm->m_Flags |= SHGF_LOWSPEC_AUTO_PRECACHE;
        break;

      case ePrecache:
        shgm->m_PrecacheNames.push_back(CParserBin::GetCRC32(data));
        shgm->m_Flags |= SHGF_PRECACHE;
        break;

      case eDependFlagSet:
        shgm->m_DependSets.push_back(data);
        break;

      case eDependFlagReset:
        shgm->m_DependResets.push_back(data);
        break;

      case eMask:
        if (data && data[0])
        {
          if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X'))
            shgm->m_Mask = shGetHex64(&data[2]);
          else
            shgm->m_Mask = shGetInt(data);
        }
        break;

      case eDependencySet:
        if (data && data[0])
        {
          if (!stricmp(data, "$LM_Diffuse"))
            shgm->m_nDependencySet |= SHGD_LM_DIFFUSE;
          else
          if (!stricmp(data, "$TEX_Detail"))
            shgm->m_nDependencySet |= SHGD_TEX_DETAIL;
          else
          if (!stricmp(data, "$TEX_Bump"))
            shgm->m_nDependencySet |= SHGD_TEX_BUMP;
          else
          if (!stricmp(data, "$TEX_Heightmap"))
            shgm->m_nDependencySet |= SHGD_TEX_HEIGHTMAP;
          else
          if (!stricmp(data, "$TEX_BumpDiffuse"))
            shgm->m_nDependencySet |= SHGD_TEX_BUMPDIF;
          else
          if (!stricmp(data, "$TEX_Gloss"))
            shgm->m_nDependencySet |= SHGD_TEX_GLOSS;
          else
          if (!stricmp(data, "$TEX_EnvCM"))
            shgm->m_nDependencySet |= SHGD_TEX_ENVCM;
          else
          if (!stricmp(data, "$TEX_Subsurface"))
            shgm->m_nDependencySet |= SHGD_TEX_SUBSURFACE;
          else
          if (!stricmp(data, "$HW_BilinearFP16"))
            shgm->m_nDependencySet |= SHGD_HW_BILINEARFP16;
          else
          if (!stricmp(data, "$HW_SeparateFP16"))
            shgm->m_nDependencySet |= SHGD_HW_SEPARATEFP16;     
          else
          if (!stricmp(data, "$TEX_Custom"))
            shgm->m_nDependencySet |= SHGD_TEX_CUSTOM;
          else
          if (!stricmp(data, "$TEX_CustomSecondary"))
            shgm->m_nDependencySet |= SHGD_TEX_CUSTOM_SECONDARY;
					else
					if (!stricmp(data, "$HW_AllowPOM"))
						shgm->m_nDependencySet |= SHGD_HW_ALLOW_POM;
          else
          if (!stricmp(data, "$HW_Tessellation"))
            shgm->m_nDependencySet |= SHGD_HW_TESSELLATION;     
		  else
			  if (!stricmp(data, "$HW_WaterTessellation"))
				  shgm->m_nDependencySet |= SHGD_HW_WATER_TESSELLATION;  
					else
					if (!stricmp(data, "$UserEnabled"))
						shgm->m_nDependencySet |= SHGD_USER_ENABLED;          					
					else
					if (!stricmp(data, "$HW_PS3"))
						shgm->m_nDependencySet |= SHGD_HW_PS3;          					
					else
					if (!stricmp(data, "$HW_X360"))
						shgm->m_nDependencySet |= SHGD_HW_X360;          					
					else
					if (!stricmp(data, "$HW_DX11"))
						shgm->m_nDependencySet |= SHGD_HW_DX11;
					else
					if (!stricmp(data, "$HW_DX10"))
						shgm->m_nDependencySet |= SHGD_HW_DX10;          					
					else
					if (!stricmp(data, "$HW_DX9"))
						shgm->m_nDependencySet |= SHGD_HW_DX9;          					
					else
						if (!stricmp(data, "$HW_CAFE"))
							shgm->m_nDependencySet |= SHGD_HW_CAFE;
          else
            assert(0);
        }
        break;

      case eDependencyReset:
        if (data && data[0])
        {
          if (!stricmp(data, "$LM_Diffuse"))
            shgm->m_nDependencyReset |= SHGD_LM_DIFFUSE;
          else
          if (!stricmp(data, "$TEX_Detail"))
            shgm->m_nDependencyReset |= SHGD_TEX_DETAIL;
          else
          if (!stricmp(data, "$TEX_Bump"))
            shgm->m_nDependencyReset |= SHGD_TEX_BUMP;
          else
          if (!stricmp(data, "$TEX_Heightmap"))
            shgm->m_nDependencyReset |= SHGD_TEX_HEIGHTMAP;
          else
          if (!stricmp(data, "$TEX_BumpDiffuse"))
            shgm->m_nDependencyReset |= SHGD_TEX_BUMPDIF;
          else
          if (!stricmp(data, "$TEX_Gloss"))
            shgm->m_nDependencyReset |= SHGD_TEX_GLOSS;
          else
          if (!stricmp(data, "$TEX_EnvCM"))
            shgm->m_nDependencyReset |= SHGD_TEX_ENVCM;
          else
          if (!stricmp(data, "$TEX_Subsurface"))
            shgm->m_nDependencyReset |= SHGD_TEX_SUBSURFACE;
          else
          if (!stricmp(data, "$HW_BilinearFP16"))
            shgm->m_nDependencyReset |= SHGD_HW_BILINEARFP16;
          else
          if (!stricmp(data, "$HW_SeparateFP16"))
            shgm->m_nDependencyReset |= SHGD_HW_SEPARATEFP16;
          else
          if (!stricmp(data, "$TEX_Custom"))
            shgm->m_nDependencyReset |= SHGD_TEX_CUSTOM;
          else
          if (!stricmp(data, "$TEX_CustomSecondary"))
            shgm->m_nDependencyReset |= SHGD_TEX_CUSTOM_SECONDARY;
          else
          if (!stricmp(data, "$TEX_Decal"))
            shgm->m_nDependencyReset |= SHGD_TEX_DECAL;
					else
					if (!stricmp(data, "$HW_AllowPOM"))
						shgm->m_nDependencyReset |= SHGD_HW_ALLOW_POM;          
					else
					if (!stricmp(data, "$HW_Tessellation"))
						shgm->m_nDependencyReset |= SHGD_HW_TESSELLATION;          
					else
					  if (!stricmp(data, "$HW_WaterTessellation"))
						  shgm->m_nDependencyReset |= SHGD_HW_WATER_TESSELLATION;  

					else
					if (!stricmp(data, "$UserEnabled"))
						shgm->m_nDependencyReset |= SHGD_USER_ENABLED;          					
					else
					if (!stricmp(data, "$HW_PS3"))
						shgm->m_nDependencyReset |= SHGD_HW_PS3;          					
					else
					if (!stricmp(data, "$HW_X360"))
						shgm->m_nDependencyReset |= SHGD_HW_X360;          					
					else
					if (!stricmp(data, "$HW_DX11"))
						shgm->m_nDependencyReset |= SHGD_HW_DX11;
					else
					if (!stricmp(data, "$HW_DX10"))
						shgm->m_nDependencyReset |= SHGD_HW_DX10;          					
					else
					if (!stricmp(data, "$HW_DX9"))
						shgm->m_nDependencyReset |= SHGD_HW_DX9;          					
					else
						if (!stricmp(data, "$HW_CAFE"))
							shgm->m_nDependencyReset |= SHGD_HW_CAFE;
          else
            assert(0);
        }
        break;
    }
  }
  shgm->m_NameLength = strlen(shgm->m_ParamName.c_str());

  return shgm;
}

bool CShaderMan::mfCompileShaderGen(SShaderGen *shg, char *scr)
{
  char* name;
  long cmd;
  char *params;
  char *data;

  SShaderGenBit *shgm;

  enum {eProperty=1, eVersion, eUsesCommonGlobalFlags};
  static STokenDesc commands[] =
  {
    {eProperty, "Property"},
    {eVersion, "Version"},
    {eUsesCommonGlobalFlags, "UsesCommonGlobalFlags"},
    {0,0}
  };

  while ((cmd = shGetObject (&scr, commands, &name, &params)) > 0)
  {
    data = NULL;
    if (name)
      data = name;
    else
    if (params)
      data = params;

    switch (cmd)
    {
      case eProperty:
        shgm = mfCompileShaderGenProperty(params);
        if (shgm)
          shg->m_BitMask.AddElem(shgm);
        break;

      case eUsesCommonGlobalFlags:
        break;

      case eVersion:
        break;
    }
  }

  return shg->m_BitMask.Num() != 0;
}

void CShaderMan::mfCompileLevelsList(std::vector<string>& List, char *scr)
{
  char* name;
  long cmd;
  char *params;
  char *data;

  enum {eName=1, eVersion};
  static STokenDesc commands[] =
  {
    {eName, "Name"},
    {0,0}
  };

  while ((cmd = shGetObject (&scr, commands, &name, &params)) > 0)
  {
    data = NULL;
    if (name)
      data = name;
    else
    if (params)
      data = params;

    switch (cmd)
    {
    case eName:
      if (data && data[0])
        List.push_back(string("Levels/") + string(data) + string("/"));
      break;
    }
  }
}

bool CShaderMan::mfCompileShaderLevelPolicies(SShaderLevelPolicies *pPL, char *scr)
{
  char* name;
  long cmd;
  char *params;
  char *data;

  enum {eGlobalList=1, ePerLevelList, eVersion};
  static STokenDesc commands[] =
  {
    {eGlobalList, "GlobalList"},
    {ePerLevelList, "PerLevelList"},
    {eVersion, "Version"},
    {0,0}
  };

  while ((cmd = shGetObject (&scr, commands, &name, &params)) > 0)
  {
    data = NULL;
    if (name)
      data = name;
    else
    if (params)
      data = params;

    switch (cmd)
    {
    case eGlobalList:
      mfCompileLevelsList(pPL->m_WhiteGlobalList, params);
      break;
    case ePerLevelList:
      mfCompileLevelsList(pPL->m_WhitePerLevelList, params);
      break;

    case eVersion:
      break;
    }
  }

  return pPL->m_WhiteGlobalList.size() != 0;
}

string CShaderMan::mfGetShaderBitNamesFromMaskGen( const char *szFileName, uint64 nMaskGen )
{
	// debug/helper function: returns ShaderGen bit names string based on nMaskGen

	if( !nMaskGen)
		return "NO_FLAGS";

	string pszShaderName = PathUtil::GetFileName( szFileName );
  pszShaderName.MakeUpper();

	uint64 nMaskGenRemaped = 0;
	ShaderMapNameFlagsItor pShaderRmp = m_pShadersGlobalFlags.find( pszShaderName.c_str() );
	if( pShaderRmp == m_pShadersGlobalFlags.end() || !pShaderRmp->second )
		return "NO_FLAGS";

	string pszShaderBitNames;
	
	// get shader bit names
	MapNameFlagsItor pIter = m_pShaderCommonGlobalFlag.begin();
	MapNameFlagsItor pEnd = m_pShaderCommonGlobalFlag.end();	
	for ( ; pIter != pEnd; ++pIter )
	{		
		if (nMaskGen & pIter->second)
			pszShaderBitNames += pIter->first.c_str();
	}

	return pszShaderBitNames;
}

void CShaderMan::mfRemapShaderGenInfoBits( const char *szName, SShaderGen *pShGen )
{
  // No data to proceed
  if( !pShGen || pShGen->m_BitMask.empty())
    return;
	
  // Check if shader uses common flags at all
	string pszShaderName = PathUtil::GetFileName(szName);
  pszShaderName.MakeUpper();
  if( (int32) m_pShadersRemapList.find( pszShaderName.c_str() ) < 0 )
    return;
  
  MapNameFlags *pOldShaderFlags = 0;
  if( m_pShadersGlobalFlags.find( pszShaderName.c_str() ) == m_pShadersGlobalFlags.end() )
  {
    pOldShaderFlags = new MapNameFlags;
    m_pShadersGlobalFlags.insert( ShaderMapNameFlagsItor::value_type( pszShaderName.c_str(),	pOldShaderFlags ) );
  }

  uint32 nBitCount = pShGen->m_BitMask.size();
  for( uint32 b = 0; b < nBitCount; ++b)
  {
    SShaderGenBit *pGenBit = pShGen->m_BitMask[b];
    if( !pGenBit )
      continue;

    // Store old shader flags
    if( pOldShaderFlags )
      pOldShaderFlags->insert( MapNameFlagsItor::value_type( pGenBit->m_ParamName.c_str(), pGenBit->m_Mask) );

    // lookup for name - and update mask value
    MapNameFlagsItor pRemaped = m_pShaderCommonGlobalFlag.find( pGenBit->m_ParamName.c_str() );
    if( pRemaped != m_pShaderCommonGlobalFlag.end() )
      pGenBit->m_Mask = pRemaped->second;

		int test = 2;
  }
}

bool CShaderMan::mfUsesGlobalFlags( const char *szShaderName )
{
  // Check if shader uses common flags at all
  string pszName = PathUtil::GetFileName(szShaderName);

  string pszShaderNameUC = pszName;
  pszShaderNameUC.MakeUpper();
  if( (int32) m_pShadersRemapList.find( pszShaderNameUC.c_str() ) < 0 )
    return false;

  return true;
}

uint64 CShaderMan::mfGetShaderGlobalMaskGenFromString( const char *szShaderGen )
{
  assert(szShaderGen);
  if( !szShaderGen || szShaderGen[0] == '\0' )
    return 0;

  uint64 nMaskGen = 0;
  MapNameFlagsItor pEnd = m_pShaderCommonGlobalFlag.end();	

  char *pCurrOffset = (char*)szShaderGen;
  while( pCurrOffset )    
  {
    char *pBeginOffset = (char*)strstr( pCurrOffset, "%" );    
    pCurrOffset = (char*)strstr( pBeginOffset + 1, "%" );

    char pCurrFlag[64]="\0";
    int nLen = pCurrOffset? pCurrOffset - pBeginOffset : strlen(pBeginOffset);
    memcpy(pCurrFlag, pBeginOffset, nLen); 

    MapNameFlagsItor pIter = m_pShaderCommonGlobalFlag.find( pCurrFlag );
    if( pIter != pEnd )
      nMaskGen |= pIter->second;
  }

  return nMaskGen;
}

const char *CShaderMan::mfGetShaderBitNamesFromGlobalMaskGen(uint64 nMaskGen )
{  
  if( !nMaskGen )
    return "\0";

  static string pszShaderBitNames;
  pszShaderBitNames = "\0";
  MapNameFlagsItor pIter = m_pShaderCommonGlobalFlag.begin();
  MapNameFlagsItor pEnd = m_pShaderCommonGlobalFlag.end();	
  for ( ; pIter != pEnd; ++pIter )
  {		
    if (nMaskGen & pIter->second)
      pszShaderBitNames += pIter->first.c_str();
  }

  return pszShaderBitNames.c_str();
}

uint64 CShaderMan::mfGetRemapedShaderMaskGen( const char *szName, uint64 nMaskGen, bool bFixup )
{  
  if( !nMaskGen )
    return 0;

  uint64 nMaskGenRemaped = 0;
  string szShaderName = PathUtil::GetFileName(szName);// some shaders might be using concatenated names (eg: terrain.layer), get only first name
  szShaderName.MakeUpper();
  ShaderMapNameFlagsItor pShaderRmp = m_pShadersGlobalFlags.find( szShaderName.c_str() );

  //char pszDebug[256];
  //sprintf( pszDebug, "checking: %s...\n", szShaderName);
  //OutputDebugString( pszDebug );

  if( pShaderRmp == m_pShadersGlobalFlags.end() || !pShaderRmp->second )
    return nMaskGen;

  if( bFixup )
  {
    // if some flag was removed, disable in mask
    nMaskGen = nMaskGen & m_nSGFlagsFix;
    return nMaskGen;
  }

  //sprintf( pszDebug, "remapping: %s\n", szShaderName);
  //OutputDebugString( pszDebug );

  // old bitmask - remap to common shared bitmasks
  for (uint64 j=0; j<64; ++j)
  {
    uint64 nMask = (((uint64)1)<<j);
    if (nMaskGen & nMask)
    {                
      MapNameFlagsItor pIter = pShaderRmp->second->begin();
      MapNameFlagsItor pEnd = pShaderRmp->second->end();
      for( ; pIter != pEnd; ++pIter )
      {
        // found match - enable bit for remapped mask
        if( (pIter->second) & nMask ) 
        {
          const char *pFlagName = pIter->first.c_str();
          MapNameFlagsItor pMatchIter = m_pShaderCommonGlobalFlag.find( pIter->first );
          if( pMatchIter != m_pShaderCommonGlobalFlag.end() )
          {
            nMaskGenRemaped |= pMatchIter->second;

            //char _debug_[256];
            //sprintf( _debug_, "%s flag %s (%I64x: %I64x remapped to %I64x)\n", szName, pFlagName, nMask, pIter->second, pMatchIter->second);
            //OutputDebugString(_debug_);
          }

          break;
        }
      }
    }
  }

  //char _debug_[256];
  //sprintf( _debug_, "%s shadergen %I64x remapped to %I64x)\n", szName, nMaskGen, nMaskGenRemaped);
  //OutputDebugString(_debug_);

  return nMaskGenRemaped;
}


SShaderGen *CShaderMan::mfCreateShaderGenInfo(const char *szName, bool bRuntime)
{
  CCryNameTSCRC s = szName;
  if (!bRuntime)
  {
    ShaderExtItor it = m_ShaderExts.find(s);
    if (it != m_ShaderExts.end())
      return it->second;
  }
  SShaderGen *pShGen = NULL;
  char szN[256];
  strcpy(szN, "Shaders/");
  strcat(szN, szName);
  strcat(szN, ".ext");
  FILE *fp = gEnv->pCryPak->FOpen(szN, "rb",ICryPak::FOPEN_HINT_QUIET);
  if (fp)
  {
    pShGen = new SShaderGen;
    gEnv->pCryPak->FSeek(fp, 0, SEEK_END);
    int ln = gEnv->pCryPak->FTell(fp);
    char *buf = new char [ln+1];
    if (buf)
    {
      buf[ln] = 0;
      gEnv->pCryPak->FSeek(fp, 0, SEEK_SET);
      gEnv->pCryPak->FRead(buf, ln, fp);
      mfCompileShaderGen(pShGen, buf);
			mfRemapShaderGenInfoBits( szName, pShGen );
      delete [] buf;
    }
    gEnv->pCryPak->FClose(fp);
  }
  if (pShGen && !bRuntime)
  {
    pShGen->m_BitMask.Shrink();
    m_ShaderExts.insert(std::pair<CCryNameTSCRC, SShaderGen*>(s, pShGen));
  }

  return pShGen;
}
